-- Title: DIY Combat Engine
-- Version: 2.5
-- Description: Combat Engine help with skill rotations, and maintaining buffs/debuffs for maximizing DPS.
-- Author: Ghost Wolf
-- Edited for Combat Engin addon by Medic
-- Diyce suports now Skill and Item ID's

local g_skill  = {}
local DIYCE_Timers = {}
local g_lastaction = ""
local g_CE_LastMessage = ""

function CE.Msg(outstr,a1,a2,a3)
	local msg = tostring(outstr),a1,a2,a3
	if msg ~= g_CE_LastMessage then
		CE_DebugFrameMessages:AddMessage(msg)
		CE_DebugFrameMessages:ScrollBottom()
		g_CE_LastMessage = msg
	else
		return
	end
	local maxLines = CE_DebugFrameMessages:GetLines()
	CE_DebugFrame_ScrollBar:SetMinMaxValues(1, maxLines)
	CE_DebugFrame_ScrollBar:SetValue(maxLines)
end

function ReadSkills()
	g_skill = {}
	local skillname,slot

	for page = 1,4 do
		slot = 1
		skillname = GetSkillDetail(page,slot)
		repeat
			local a1,a2,a3,a4,a5,a6,a7,a8,skillusable = GetSkillDetail(page,slot)
			if skillusable then
				g_skill[skillname] = { ["page"] = page, ["slot"] = slot }
			end
			slot = slot + 1
			skillname = GetSkillDetail(page,slot)
		until skillname == nil
	end
end

-- Read Skills on Log-In/Class Change/Level-Up
		local DIYCE_EventFrame = CreateUIComponent("Frame","DIYCE_EventFrame","UIParent")
			DIYCE_EventFrame:SetScripts("OnEvent", [=[ 
					if event == "PLAYER_SKILLED_CHANGED" then
						ReadSkills()
						end
					]=] )
			DIYCE_EventFrame:RegisterEvent("PLAYER_SKILLED_CHANGED")
			DIYCE_EventFrame:SetScripts("OnUpdate", [=[ DIYCE_TimerUpdate(elapsedTime) ]=] )
			
function PctH(tgt)
    return (UnitHealth(tgt)/UnitMaxHealth(tgt))
end

function PctM(tgt)
    return (UnitMana(tgt)/UnitMaxMana(tgt))
end

function PctS(tgt)
    return (UnitSkill(tgt)/UnitMaxSkill(tgt))
end

function CancelBuff(buffname)
    local i = 1
    local buff = UnitBuff("player",i)
	
	local buff = string.gsub(buff, "(Function:)( *)(.*)", "%3")
	
	while buff ~= nil do
		if buff == buffname then
			CancelPlayerBuff(i)
			return true
		end

        i = i + 1
        buff = UnitBuff("player",i)
    end
    return false
end

function BuffList(tgt)
    local list = {}
    local buffcmd = UnitBuff
    local infocmd = UnitBuffLeftTime

    if UnitCanAttack("player",tgt) then
        buffcmd = UnitDebuff
        infocmd = UnitDebuffLeftTime
    end

    -- There is a max of 100 buffs/debuffs per unit apparently
    for i = 1,100 do
        local buff, _, stackSize, ID = buffcmd(tgt, i)
        local timeRemaining = infocmd(tgt,i)
        if buff then
            -- Ad to list by name
            list[buff:gsub("(%()(.)(%))", "%2")] = { stack = stackSize, time = timeRemaining or 0, id = ID }
            -- We also list by ID in case two different buffs/debuffs have the same name.
            list[ID] = {stack = stackSize, time = timeRemaining, name = buff:gsub("(%()(.)(%))", "%2") }
        else
            break
        end
    end

    return list
end

--		Example of useage of ChkBuffCount()
--			{ name = "Tactical Attack",   use = ((EnergyBar1 >= 15) and (ChkBuffCount("target",_,500081,2))) },
function ChkBuffCount(tgt,buffname,BuffID,buffcount)
    local cnt = 1
    local buffcmd = UnitBuff
    local buffcounter = 0

    if UnitCanAttack("player",tgt) then
        buffcmd = UnitDebuff
    end
    
	local buff, _, _, ID = buffcmd(tgt,cnt)

    while buff ~= nil do
        if string.gsub(buff, "(%()(.)(%))", "%2") == buffname
            or ID == BuffID then
            buffcounter = buffcounter + 1        
        end
        cnt = cnt + 1
        buff = buffcmd(tgt,cnt)
        
        if buffcounter >= buffcount then
            return true
        end
    end    
end

function CD(skillname)
    local firstskill = GetSkillDetail(2,1)
    if (g_skill[firstskill] == nil) or (g_skill[firstskill].page ~= 2) then
        ReadSkills()
    end

	if g_skill[skillname] ~= nil then
		local tt,cd = GetSkillCooldown(g_skill[skillname].page,g_skill[skillname].slot)
		return cd <= 0.2
	elseif skillname == nil then
		return false
	else
        return
    end
end

function MyCombat(Skill, arg1)
    local spell_name = UnitCastingTime("player")
    local talktome = ((arg1 == "v1") or (arg1 == "v2"))
    local action,actioncd,actiondef,actioncnt
    
    if spell_name ~= nil then
        if (arg1 == "v2") then CE.Msg("["..spell_name.."]", 0, 1, 1) end
        return true
    end

    for x,tbl in ipairs(Skill) do
        local useit = type(Skill[x].use) ~= "function" and Skill[x].use or (type(Skill[x].use) == "function" and Skill[x].use() or false)
        
		if useit then
            if string.find(Skill[x].name, "Action:") then
                action = tonumber((string.gsub(Skill[x].name, "(Action:)( *)(%d+)(.*)", "%3")))
                _1,actioncd = GetActionCooldown(action)
                actiondef,_1,actioncnt = GetActionInfo(action)
                if GetActionUsable(action) and (actioncd == 0) and (actiondef ~= nil) and (actioncnt > 0) then
                    if talktome then CE.Msg("["..Skill[x].name.."]") end
                    UseAction(action)
                    return true
                end
            elseif string.find(Skill[x].name, "Custom:") then
                action = string.gsub(Skill[x].name, "(Custom:)( *)(.*)", "%3")
                if CustomAction(action) then
                    return true
                end
            elseif string.find(Skill[x].name, "Item:") then
                action = string.gsub(Skill[x].name, "(Item:)( *)(.*)", "%3")
                if talktome then CE.Msg(""..Skill[x].name) end
                UseItemByName(action)
                return true
			elseif string.find(Skill[x].name, "ItemID:") then
                action = string.gsub(Skill[x].name, "(ItemID:)( *)(.*)", "Sys%3_name")
				if (Skill[x].ignoretimer or GetDIYCETimerValue(Skill[x].timer) == 0) then	-- this is added
					if talktome then CE.Msg(""..TEXT(action)) end
					UseItemByName(TEXT(action))
					StartDIYCETimer(Skill[x].timer)
					return true
				end																			-- this is added
                return true
			elseif string.find(Skill[x].name, "ID: ") then
				action = string.gsub(Skill[x].name,"(ID:)( *)(.*)", "Sys%3_name")
				if (Skill[x].ignoretimer or GetDIYCETimerValue(Skill[x].timer) == 0) and CD(TEXT(action)) then
					if talktome then CE.Msg("["..TEXT(action).."]") end
					CastSpellByName(TEXT(action))
					StartDIYCETimer(Skill[x].timer)
					return true
				end
            elseif (Skill[x].ignoretimer or GetDIYCETimerValue(Skill[x].timer) == 0) and CD(Skill[x].name) then
                if talktome then CE.Msg(""..Skill[x].name) end
                CastSpellByName(Skill[x].name)
                StartDIYCETimer(Skill[x].timer)
                return true
            elseif string.find(Skill[x].name, "Pet Skill:") then
                action = string.gsub(Skill[x].name, "(Pet Skill:)( *)(%d+)(.*)", "%3")
                    UsePetAction(action)
                if (arg1 == "v2") then CE.Msg(Skill[x].name.." has been fully processed") end
                return true
			elseif string.find(Skill[x].name, "Func:") then
                action = string.gsub(Skill[x].name, "(Func:)( *)(.*)", "%3")
                if (Skill[x].ignoretimer or GetDIYCETimerValue(Skill[x].timer) == 0) then
					if talktome then CE.Msg(""..Skill[x].name) end
					RunScript(action)
					StartDIYCETimer(Skill[x].timer)
					return true
				end
			end
		end
	end
	if (arg1 == "v2") then CE.Msg("[IDLE]", 0, 1, 1) end

	return false
end

function BuffTimeLeft(tgt, buffname)
    local cnt = 1
	local buff = UnitBuff(tgt,cnt)

	while buff ~= nil do
		if string.find(buff,buffname) then
			return UnitBuffLeftTime(tgt,cnt)
		end
		cnt = cnt + 1
		buff = UnitBuff(tgt,cnt)
	end

	return 0
end

function BuffParty(arg1,arg2)
--    arg1 = Quickbar slot # for targetable, instant-cast buff without a cooldown (eg. Amp Attack) for range checking.
--    arg2 = buff expiration time cutoff (in seconds) for refreshing buffs, default is 45 seconds.

    local selfbuffs = { "Soul Bond", "Enhanced Armor", "Holy Seal" }
    local groupbuffs = { "Grace of Life", "Amplified Attack", "Angel's Blessing", "Essence of Magic", "Magic Barrier", "Fire Ward", "Savage Blessing", "Concentration Prayer", "Shadow Fury", "Embrace of the Water Spirit"  }

    local buffrefresh = arg2 or 45           -- Refresh buff time (seconds)
    local spell = UnitCastingTime("player")  -- Spell being cast?
    local vocal = IsShiftKeyDown()           -- Generate feedback if Shift key held

    if (spell ~= nil) then
        return
    end

    if vocal then CE.Msg("- Checking self buffs on "..UnitName("player")) end
    for i,buff in ipairs(selfbuffs) do
        if (g_skill[buff] ~= nil) and CD(buff) and (BuffTimeLeft("player",buff) <= buffrefresh) then
            if vocal then CE.Msg("- Casting "..buff.." on "..UnitName("player")) end
            TargetUnit("player")
            CastSpellByName(buff)
            return
        end
    end

    if vocal then CE.Msg("- Checking group buffs on "..UnitName("player")) end
    for i,buff in ipairs(groupbuffs) do
        if (g_skill[buff] ~= nil) and CD(buff) and (BuffTimeLeft("player",buff) <= buffrefresh) then
            if vocal then CE.Msg("- Casting "..buff.." on "..UnitName("player")) end
            TargetUnit("player")
            CastSpellByName(buff)
            return
        end
    end

    for num=1,GetNumPartyMembers()-1 do
        TargetUnit("party"..num)
        if GetActionUsable(arg1) and (UnitHealth("party"..num) > 0) then
            if vocal then CE.Msg("- Checking group buffs on "..UnitName("party"..num)) end
            for i,buff in ipairs(groupbuffs) do
                if (g_skill[buff] ~= nil) and CD(buff) and (BuffTimeLeft("target",buff) <= buffrefresh) then
                    if UnitIsUnit("target","party"..num) then
                        if vocal then CE.Msg("- Casting "..buff.." on "..UnitName("target")) end
                        CastSpellByName(buff)
                        return
                    else
                        if vocal then CE.Msg("- Error: "..UnitName("target").." != "..UnitName("party"..num)) end
                    end
                end
            end
        else
            if vocal then CE.Msg("- Player "..UnitName("party"..num).." out of range or dead.") end
        end
    end

    if vocal then CE.Msg("- Nothing to do.") end			
end

--[[ Timer Update function ]]--
-- Tick down any active timers
function DIYCE_TimerUpdate(elapsed)
    for k,v in pairs(DIYCE_Timers) do
        v.timeLeft = v.timeLeft - elapsed
        if v.timeLeft < 0 then
            v.timeLeft = 0
        end
    end
end

--[[ Create a named timer ]]--
-- if the named timer already exists, this does nothing.
function CreateDIYCETimer(timerName, waitTime)
    if not DIYCE_Timers[timerName] then
        DIYCE_Timers[timerName] = { timeLeft = 0, waitTime = waitTime }
    end
end

--[[ Set/reset waitTimer of an existing timer ]]--
-- if the timer doesn't exist, this does nothing
function SetDIYCETimerDelay(timerName, waitTime)
    if DIYCE_Timers[timerName] then
        DIYCE_Timers[timerName].waitTime = waitTime
    end
end

--[[ Delete named timer ]]--
-- if the timer doesn't exist, this does nothing
-- Not really needed, but added for completeness
function DeleteDIYCETimer(timerName)
    if DIYCE_Timers[timerName] then
        DIYCE_Timers[timerName] = nil
    end
end

--[[ Get a timer's current time ]]--
-- if the timer doesn't exist, this returns 0
function GetDIYCETimerValue(timerName)
    if timerName then
        return DIYCE_Timers[timerName] and DIYCE_Timers[timerName].timeLeft or 0
    end
    return 0
end

--[[ Starts a timer ticking down ]]--
-- if timer doesn't exist, this does nothing
function StartDIYCETimer(timerName)
    if timerName and DIYCE_Timers[timerName] then
        DIYCE_Timers[timerName].timeLeft = DIYCE_Timers[timerName].waitTime
    end
end

--[[ Stop a named timer ]]--
-- if timer doesn't exist, this does nothing
function StopDIYCETimer(timerName)
   if DIYCE_Timers[timerName] then
      DIYCE_Timers[timerName].timeLeft = 0
  end
end

function CustomAction(action)
    if CD(action) then
        if IsShiftKeyDown() then CE.Msg("- "..action) end
        g_lastaction = action
        CastSpellByName(action)
        return true
    else
        return false
    end
end
